package com.nervenets.web.redis;

import com.nervenets.Global;
import com.nervenets.web.enumeration.OperateType;
import com.nervenets.web.enumeration.WealthType;
import com.nervenets.web.hibernate.dao.*;
import com.nervenets.web.hibernate.domain.*;
import com.nervenets.web.hibernate.domain.user.User;
import com.nervenets.web.redis.domain.admin.ManagerStored;
import com.nervenets.web.redis.domain.common.TypeStored;
import com.nervenets.web.redis.domain.user.UserStored;
import com.nervenets.web.redis.domain.user.WealthOfMoney;
import com.nervenets.web.redis.domain.user.WealthOfMoneyByOperateType;
import com.nervenets.web.redis.domain.user.WealthOfTotalMoney;
import com.nervenets.web.redis.utils.RedisSyncLocker;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;

/**
 * Created by Joe on 2018/1/5.
 */
@Service
public class GlobalService {
    @Resource
    private RedisDao redisDao;
    @Resource
    private RedisSetDao redisSetDao;
    @Resource
    private RedisOrderedSetDao redisOrderedSetDao;
    @Resource
    private UserDao userDao;
    @Resource
    private TypeDao typeDao;
    @Resource
    private ManagerDao managerDao;
    @Resource
    private WealthFlowDao wealthFlowDao;
    @Resource
    private RedisSyncLocker redisSyncLocker;
    @Resource
    private RedisTemplate redisTemplate;

    public TypeStored getType(long typeId) {
        TypeStored ts = new TypeStored(typeId);
        if (redisSetDao.exists(ts)) {
            return redisSetDao.getRedisSetDomain(ts);
        } else {
            Type type = typeDao.findById(typeId).orElse(null);
            if (null != type) {
                ts = new TypeStored(type);
                redisSetDao.saveOrUpdate(ts);
                return ts;
            }
        }
        return null;
    }

    public void clearRedisTypeStored(long typeId) {
        TypeStored ms = new TypeStored(typeId);
        redisSetDao.delete(ms);
    }

    public ManagerStored getManager(long managerId) {
        ManagerStored ms = new ManagerStored(managerId);
        if (redisSetDao.exists(ms)) {
            return redisSetDao.getRedisSetDomain(ms);
        } else {
            Manager manager = managerDao.findById(managerId).orElse(null);
            if (null != manager) {
                ms = new ManagerStored(manager);
                redisSetDao.saveOrUpdate(ms);
                return ms;
            }
        }
        return null;
    }

    public void clearRedisManagerStored(long managerId) {
        ManagerStored ms = new ManagerStored(managerId);
        redisSetDao.delete(ms);
    }

    public UserStored getUser(long userId) {
        UserStored us = new UserStored(userId);
        if (redisSetDao.exists(us)) {
            return redisSetDao.getRedisSetDomain(us);
        } else {
            User user = userDao.findById(userId).orElse(null);
            if (null != user) {
                us = new UserStored(user);
                redisSetDao.saveOrUpdate(us);
                return us;
            }
        }
        return null;
    }

    public void clearRedisUserStored(long userId) {
        UserStored usSelect = new UserStored(userId);
        redisSetDao.delete(usSelect);
    }

    /**
     * 设置全局键值对
     *
     * @param key   键
     * @param value 值
     * @throws Exception
     */
    public void setGlobalPair(String key, String value) {
        redisDao.addString(String.format(Global.redis_prefix_format, key), value);
    }

    public void setGlobalPair(String key, String value, int expire) {
        redisDao.addString(String.format(Global.redis_prefix_format, key), value, expire);
    }

    public void delGlobalPair(String key) {
        redisDao.delete(key);
    }

    /**
     * 获取全局键值对
     *
     * @param key 键
     * @return 值
     * @throws Exception
     */
    public String getGlobalPair(String key) throws Exception {
        return redisDao.getString(String.format(Global.redis_prefix_format, key));
    }

    public long getGlobalPairExpired(String key) throws Exception {
        return redisDao.getExpired(String.format(Global.redis_prefix_format, key));
    }

    public void clearRedis() {
        redisTemplate.execute((RedisCallback) rc -> {
            rc.flushDb();
            rc.flushAll();
            return true;
        });
    }


    public long getUserWealthOfMoney(long userId, WealthType wealthType) {
        WealthOfMoney wealthOfMoney = new WealthOfMoney(userId, wealthType);
        Double zScore = redisOrderedSetDao.getZScore(wealthOfMoney);
        if (null == zScore) {
            final WealthFlow top = wealthFlowDao.findTopByUserIdAndWealthTypeOrderByIdDesc(userId, wealthType);
            long surplus = null == top ? 0L : top.getSurplus();
            zScore = redisOrderedSetDao.incrBy(wealthOfMoney, surplus);
        }
        return zScore.longValue();
    }

    public long addUserWealthOfMoney(long userId, WealthType wealthType, long offset) {
        WealthOfMoney wealthOfMoney = new WealthOfMoney(userId, wealthType);
        Double zScore = redisOrderedSetDao.getZScore(wealthOfMoney);
        if (null == zScore) {
            final WealthFlow top = wealthFlowDao.findTopByUserIdAndWealthTypeOrderByIdDesc(userId, wealthType);
            long surplus = null == top ? 0L : top.getSurplus();
            redisOrderedSetDao.incrBy(wealthOfMoney, surplus);
        }
        zScore = redisOrderedSetDao.incrBy(wealthOfMoney, offset);
        return zScore.longValue();
    }

    public void removeRedisWealthOfMoney(long userId, WealthType wealthType) {
        WealthOfMoney wealthOfMoney = new WealthOfMoney(userId, wealthType);
        redisOrderedSetDao.delete(wealthOfMoney);
    }

    public long getUserWealthOfTotalMoney(long userId, WealthType wealthType) {
        WealthOfTotalMoney wealthOfTotalMoney = new WealthOfTotalMoney(userId, wealthType);
        Double zScore = redisOrderedSetDao.getZScore(wealthOfTotalMoney);
        if (null == zScore) {
            BigDecimal totalGet = wealthFlowDao.getTotalGet(userId, wealthType);
            zScore = redisOrderedSetDao.incrBy(wealthOfTotalMoney, null == totalGet ? 0 : totalGet.longValue());
        }
        return zScore.longValue();
    }

    public long addUserWealthOfTotalMoney(long userId, WealthType wealthType, long offset) {
        if (offset <= 0) return getUserWealthOfMoney(userId, wealthType);
        WealthOfTotalMoney wealthOfTotalMoney = new WealthOfTotalMoney(userId, wealthType);
        Double zScore = redisOrderedSetDao.getZScore(wealthOfTotalMoney);
        if (null == zScore) {
            BigDecimal totalGet = wealthFlowDao.getTotalGet(userId, wealthType);
            redisOrderedSetDao.incrBy(wealthOfTotalMoney, null == totalGet ? 0 : totalGet.longValue());
        }
        zScore = redisOrderedSetDao.incrBy(wealthOfTotalMoney, offset);
        return zScore.longValue();
    }

    public void removeRedisWealthOfTotalMoney(long userId, WealthType wealthType) {
        WealthOfTotalMoney wealthOfTotalMoney = new WealthOfTotalMoney(userId, wealthType);
        redisOrderedSetDao.delete(wealthOfTotalMoney);
    }

    public void addUserWealthOfMoneyByOperateType(long userId, WealthType wealthType, OperateType operateType, long offset) {
        WealthOfMoneyByOperateType wealthOfMoneyByOperateType = new WealthOfMoneyByOperateType(userId, operateType, wealthType);
        if (redisSetDao.exists(wealthOfMoneyByOperateType)) {
            wealthOfMoneyByOperateType = redisSetDao.getRedisSetDomain(wealthOfMoneyByOperateType);
        }
        wealthOfMoneyByOperateType.add(offset);
        redisSetDao.saveOrUpdate(wealthOfMoneyByOperateType);
    }

    public WealthOfMoneyByOperateType getUserWealthOfMoneyByOperateType(long userId, OperateType operateType, WealthType wealthType) {
        WealthOfMoneyByOperateType wealthOfMoneyByOperateType = new WealthOfMoneyByOperateType(userId, operateType, wealthType);
        if (redisSetDao.exists(wealthOfMoneyByOperateType)) {
            wealthOfMoneyByOperateType = redisSetDao.getRedisSetDomain(wealthOfMoneyByOperateType);
        } else {
            final BigDecimal total = wealthFlowDao.getTotalByOperateTypeAndWealthType(userId, operateType, wealthType);
            final Long count = wealthFlowDao.countByUserIdAndOperateTypeAndWealthType(userId, operateType, wealthType);
            wealthOfMoneyByOperateType.setTotal(total.longValue());
            wealthOfMoneyByOperateType.setTimes(Math.toIntExact(count));
            redisSetDao.saveOrUpdate(wealthOfMoneyByOperateType);
        }
        return wealthOfMoneyByOperateType;
    }
}
